package math.visitors;

import static math.ExpressionBuilder.add;
import static math.ExpressionBuilder.and;
import static math.ExpressionBuilder.beq;
import static math.ExpressionBuilder.cos;
import static math.ExpressionBuilder.exp;
import static math.ExpressionBuilder.ln;
import static math.ExpressionBuilder.mult;
import static math.ExpressionBuilder.neg;
import static math.ExpressionBuilder.not;
import static math.ExpressionBuilder.or;
import static math.ExpressionBuilder.pow;
import static math.ExpressionBuilder.sin;
import static math.ExpressionBuilder.xor;
import math.Add;
import math.And;
import math.ArithmeticError;
import math.ArithmeticExpression;
import math.BooleanConstant;
import math.BooleanEquality;
import math.BooleanExpression;
import math.BooleanVariable;
import math.Cos;
import math.Evaluation;
import math.Exp;
import math.Expression;
import math.ExpressionVisitor;
import math.IntegerConstant;
import math.Ln;
import math.LogicalArithmeticVariable;
import math.LogicalBooleanVariable;
import math.Mult;
import math.Negation;
import math.Not;
import math.NumericLogicalVariable;
import math.Or;
import math.PI;
import math.Pow;
import math.Sin;
import math.Variable;
import math.Xor;

public class ReplaceVisitor implements ExpressionVisitor<Expression> {

  private Expression oldExp;

  private Expression newExp;

  public ReplaceVisitor(Expression old, Expression newExp) {
    this.oldExp = old;
    this.newExp = newExp;
  }

  @Override
  public Expression visitAnd(And and) {
    if (oldExp == and)
      return newExp;
    BooleanExpression arg1 = and.getArg1();
    BooleanExpression arg2 = and.getArg2();
    BooleanExpression newArg1 = (BooleanExpression) arg1.accept(this);
    BooleanExpression newArg2 = (BooleanExpression) arg2.accept(this);
    if (newArg1 == arg1 && newArg2 == arg2)
      return and;
    else
      return and(newArg1, newArg2);
  }

  @Override
  public Expression visitBooleanConstant(BooleanConstant booleanConstant) {
    if (oldExp == booleanConstant)
      return newExp;
    else
      return booleanConstant;
  }

  @Override
  public Expression visitBooleanVariable(BooleanVariable booleanVariable) {
    if (oldExp == booleanVariable)
      return newExp;
    else
      return booleanVariable;
  }

  @Override
  public Expression visitOr(Or or) {
    if (oldExp == or)
      return newExp;
    BooleanExpression arg1 = or.getArg1();
    BooleanExpression arg2 = or.getArg2();
    BooleanExpression newArg1 = (BooleanExpression) arg1.accept(this);
    BooleanExpression newArg2 = (BooleanExpression) arg2.accept(this);
    if (newArg1 == arg1 && newArg2 == arg2)
      return or;
    else
      return or(newArg1, newArg2);
  }

  @Override
  public Expression visitXor(Xor xor) {
    if (oldExp == xor)
      return newExp;
    BooleanExpression arg1 = xor.getArg1();
    BooleanExpression arg2 = xor.getArg2();
    BooleanExpression newArg1 = (BooleanExpression) arg1.accept(this);
    BooleanExpression newArg2 = (BooleanExpression) arg2.accept(this);
    if (newArg1 == arg1 && newArg2 == arg2)
      return xor;
    else
      return xor(newArg1, newArg2);
  }

  @Override
  public Expression visitAdd(Add add) {
    if (oldExp == add)
      return newExp;
    ArithmeticExpression addend1 = add.getAddend1();
    ArithmeticExpression addend2 = add.getAddend2();
    ArithmeticExpression newAddend1 = (ArithmeticExpression) addend1
        .accept(this);
    ArithmeticExpression newAddend2 = (ArithmeticExpression) addend2
        .accept(this);
    if (newAddend1 == addend1 && newAddend2 == addend2)
      return add;
    else
      return add(newAddend1, newAddend2);
  }

  @Override
  public Expression visitCos(Cos cos) {
    if (oldExp == cos)
      return newExp;
    ArithmeticExpression argument = cos.getArgument();
    ArithmeticExpression newArgument = (ArithmeticExpression) argument.accept(this);
    if (newArgument == argument)
      return cos;
    else
      return cos(newArgument);
  }

  @Override
  public Expression visitEvaluation(Evaluation evaluation) {
    if (oldExp == evaluation)
      return newExp;
    else
      return evaluation;
  }

  @Override
  public Expression visitExp(Exp exp) {
    if (oldExp == exp)
      return newExp;
    ArithmeticExpression argument = exp.getArgument();
    ArithmeticExpression newArgument = (ArithmeticExpression) argument.accept(this);
    if (newArgument == argument)
      return exp;
    else
      return exp(newArgument);
  }

  @Override
  public Expression visitIntegerConstant(IntegerConstant value) {
    if (oldExp == value)
      return newExp;
    else
      return value;
  }

  @Override
  public Expression visitLn(Ln ln) {
    if (oldExp == ln)
      return newExp;
    ArithmeticExpression argument = ln.getArgument();
    ArithmeticExpression newArgument = (ArithmeticExpression) argument.accept(this);
    if (newArgument == argument)
      return ln;
    else
      return ln(newArgument);
  }

  @Override
  public Expression visitLogicalArithmeticVariable(LogicalArithmeticVariable logicalVariable) {
    if (oldExp == logicalVariable)
      return newExp;
    else
      return logicalVariable;
  }

  @Override
  public Expression visitLogicalBooleanVariable(LogicalBooleanVariable logicalVariable) {
    if (oldExp == logicalVariable)
      return newExp;
    else
      return logicalVariable;
  }

  @Override
  public Expression visitMult(Mult mult) {
    if (oldExp == mult)
      return newExp;
    ArithmeticExpression factor1 = mult.getFactor1();
    ArithmeticExpression factor2 = mult.getFactor2();
    ArithmeticExpression newFactor1 = (ArithmeticExpression) factor1
        .accept(this);
    ArithmeticExpression newFactor2 = (ArithmeticExpression) factor2
        .accept(this);
    if (newFactor1 == factor1 && newFactor2 == factor2)
      return mult;
    else
      return mult(newFactor1, newFactor2);
  }

  @Override
  public Expression visitNegation(Negation negation) {
    if (oldExp == negation)
      return newExp;
    ArithmeticExpression argument = negation.getArgument();
    ArithmeticExpression newArgument = (ArithmeticExpression) argument.accept(this);
    if (newArgument == argument)
      return negation;
    else
      return neg(newArgument);
  }

  @Override
  public Expression visitNumericalLogicalVariable(
      NumericLogicalVariable numericLogicalVariable) {
    if (oldExp == numericLogicalVariable)
      return newExp;
    else
      return numericLogicalVariable;
  }

  @Override
  public Expression visitPI(PI pi) {
    if (oldExp == pi)
      return newExp;
    else
      return pi;
  }

  @Override
  public Expression visitPow(Pow pow) {
    if (oldExp == pow)
      return newExp;
    ArithmeticExpression base = pow.getBase();
    ArithmeticExpression exponent = pow.getExponent();
    ArithmeticExpression newBase = (ArithmeticExpression) base
        .accept(this);
    ArithmeticExpression newExponent = (ArithmeticExpression) exponent
        .accept(this);
    if (newBase == base && newExponent == exponent)
      return pow;
    else
      return pow(newBase, newExponent);
  }

  @Override
  public Expression visitSin(Sin sin) {
    if (oldExp == sin)
      return newExp;
    ArithmeticExpression argument = sin.getArgument();
    ArithmeticExpression newArgument = (ArithmeticExpression) argument.accept(this);
    if (newArgument == argument)
      return sin;
    else
      return sin(newArgument);
  }

  @Override
  public Expression visitVariable(Variable var) {
    if (oldExp == var)
      return newExp;
    else
      return var;
  }

  @Override
  public Expression visitNot(Not not) {
    if (oldExp == not)
      return newExp;
    BooleanExpression argument = not.getArgument();
    BooleanExpression newArgument = (BooleanExpression) argument.accept(this);
    if (newArgument == argument)
      return not;
    else
      return not(newArgument);
  }

  @Override
  public Expression visitArithmeticError(ArithmeticError arithmeticError) {
    if (oldExp == arithmeticError)
      return newExp;
    else
      return arithmeticError;
  }

  @Override
  public Expression visitBooleanEquality(BooleanEquality booleanEquality) {
    if (oldExp == booleanEquality)
      return newExp;
    BooleanExpression arg1 = booleanEquality.getArg1();
    BooleanExpression arg2 = booleanEquality.getArg2();
    BooleanExpression newArg1 = (BooleanExpression) arg1.accept(this);
    BooleanExpression newArg2 = (BooleanExpression) arg2.accept(this);
    if (newArg1 == arg1 && newArg2 == arg2)
      return booleanEquality;
    else
      return beq(newArg1, newArg2);
  }
}